﻿/*
 *  Copyright 2009 Alessandro Pilotti
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation; either version 2.1 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA 
 */

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using System.Data.Linq.Mapping;
using System.Data.Linq;
using System.Linq;
using AlexPilotti.L2SQLDataModel.SchemaInfo;
using System.Runtime.InteropServices;
using System.IO;


namespace AlexPilotti.L2SQLDataModel
{
    public class L2SQLDataModelGenerator
    {
        class RelationInfo
        {
            public TableClassInfo PKClassInfo { get; set; }
            public TableClassInfo FKClassInfo { get; set; }
            public PropertyBuilder OneToManyPropertyBuilder { get; set; }
            public PropertyBuilder ManyToOnePropertyBuilder { get; set; }
            public MethodBuilder AttachMethod { get; set; }
            public MethodBuilder DetachMethod { get; set; }
        }

        class TableClassInfo
        {
            public TypeBuilder TypeBuilder { get; set; }
            public TableInfo TableInfo { get; set; }

            /// <summary>
            /// Needed because Type.GetProperties() does not work on dynamic types.
            /// The key is the property name.
            /// </summary>
            public IDictionary<string, PropertyBuilder> Properties { get; set; }

            /// <summary>
            /// Needed because Type.GetFields() does not work on dynamic types.
            /// The key is the property name.
            /// </summary>
            public IDictionary<string,FieldBuilder> Fields { get; set; }

            /// <summary>
            /// Maps table column names to class properties
            /// </summary>
            public IDictionary<string, PropertyBuilder> ColumnNameProperty { get; set; }

            public TableClassInfo(TypeBuilder tb, TableInfo ti)
            {
                this.TypeBuilder = tb;
                this.TableInfo = ti;
                this.Properties = new Dictionary<string, PropertyBuilder>();
                this.Fields = new Dictionary<string, FieldBuilder>();
                this.ColumnNameProperty = new Dictionary<string, PropertyBuilder>();
            }
        }

        const string fieldPrefix = "_";
        const string dataContextName = "MainDataContext";
        const string attachPrefix = "attach_";
        const string detachPrefix = "detach_";
        
        ConstructorInfo entitySetActionTCtorInfo;

        public event DBSchemaInfoErrorEventHandler HandleError;

        public L2SQLDataModelGenerator()
        {
            // Get the ConstructorInfo for EntitySet<T>(Action<T>, Action<T>)
            entitySetActionTCtorInfo = (from s in typeof(EntitySet<>).GetConstructors() where 
                                       s.GetParameters().Length == 2 && s.GetParameters().All(a => a.ParameterType.IsGenericType && 
                                                                                              a.ParameterType.GetGenericTypeDefinition() == typeof(Action<>)) 
                                       select s).Single();
        }

        /// <summary>
        /// Generates the assembly.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <param name="dir">The dir.</param>
        /// <param name="dbSchemaInfo">The db schema info.</param>
        /// <param name="ns">The ns.</param>
        /// <param name="snKeyPairFilePath">An optional strong name key pair used to sign the assembly. Usually generated with <c>sn -k &lt;filename&gt;</c>.</param>
        public void GenerateAssembly(string name, string dir, IDBSchemaInfo dbSchemaInfo, string ns, 
                                     bool includeTables, bool includeViews, bool includeProcedures,
                                     string snKeyPairFilePath)
        {
            AssemblyName assemblyName = new AssemblyName(name);
            assemblyName.Version = new Version(1, 0, 0, 0);

            if (snKeyPairFilePath != null)
                assemblyName.KeyPair = new StrongNameKeyPair(File.Open(snKeyPairFilePath, FileMode.Open, FileAccess.Read));

            string assemblyDllName = assemblyName.Name + ".dll";

            AssemblyBuilder ab = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave, dir);            
            ModuleBuilder mb = ab.DefineDynamicModule(assemblyName.Name, assemblyDllName);

            IDictionary<string, TypeBuilder> typeBuilders = new Dictionary<string, TypeBuilder>();

            TypeBuilder tbDataContext = EmitDataContext(mb, GetTypeFullName(ns, dataContextName));
            typeBuilders.Add(tbDataContext.FullName, tbDataContext);

            TableViewType tableViewType = 0;
            if (includeTables)
                tableViewType |= TableViewType.Table;
            if (includeViews)
                tableViewType |= TableViewType.View;

            if (tableViewType != 0)
            {
                IList<TableInfo> tablesAndViewsInfoList = dbSchemaInfo.GetTablesAndViewsInfo(null, tableViewType);
                IList<TableClassInfo> tablesAndViewsToEmit = EmitTables(mb, tablesAndViewsInfoList, ns, typeBuilders);
                EmitGetTableProperties(tbDataContext, tablesAndViewsToEmit);
            }

            if (includeProcedures)
            {
                IList<ProcedureInfo> procInfoList = dbSchemaInfo.GetProceduresInfo(null);
                EmitProceduresAndFunctions(mb, tbDataContext, procInfoList, ns, typeBuilders);
            }

            tbDataContext.CreateType();

            SetAssemblyAttributes(assemblyName, ab);            

            ab.Save(assemblyDllName);
        }

        private static void SetAssemblyAttributes(AssemblyName assemblyName, AssemblyBuilder ab)
        {
            CustomAttributeBuilder cab = DynamicAssemblyUtility.EmitAttribute(typeof(AssemblyFileVersionAttribute), new Type[] { typeof(string) }, new object[] { assemblyName.Version.ToString() });
            ab.SetCustomAttribute(cab);

            cab = DynamicAssemblyUtility.EmitAttribute(typeof(AssemblyTitleAttribute), new Type[] { typeof(string) }, new object[] { assemblyName.Name });
            ab.SetCustomAttribute(cab);

            cab = DynamicAssemblyUtility.EmitAttribute(typeof(AssemblyDescriptionAttribute), new Type[] { typeof(string) }, new object[] { "Assembly dynamically generated by PowerQuery" });
            ab.SetCustomAttribute(cab);

            cab = DynamicAssemblyUtility.EmitAttribute(typeof(AssemblyConfigurationAttribute), new Type[] { typeof(string) }, new object[] { "" });
            ab.SetCustomAttribute(cab);

            cab = DynamicAssemblyUtility.EmitAttribute(typeof(AssemblyCompanyAttribute), new Type[] { typeof(string) }, new object[] { "" });
            ab.SetCustomAttribute(cab);

            cab = DynamicAssemblyUtility.EmitAttribute(typeof(AssemblyProductAttribute), new Type[] { typeof(string) }, new object[] { assemblyName.Name });
            ab.SetCustomAttribute(cab);

            cab = DynamicAssemblyUtility.EmitAttribute(typeof(AssemblyCopyrightAttribute), new Type[] { typeof(string) }, new object[] { string.Format("Copyright \x00a9  {0})", DateTime.Now.Year) });
            ab.SetCustomAttribute(cab);

            cab = DynamicAssemblyUtility.EmitAttribute(typeof(AssemblyTrademarkAttribute), new Type[] { typeof(string) }, new object[] { "" });
            ab.SetCustomAttribute(cab);

            cab = DynamicAssemblyUtility.EmitAttribute(typeof(ComVisibleAttribute), new Type[] { typeof(bool) }, new object[] { false });
            ab.SetCustomAttribute(cab);

            cab = DynamicAssemblyUtility.EmitAttribute(typeof(GuidAttribute), new Type[] { typeof(string) }, new object[] { Guid.NewGuid().ToString() });
            ab.SetCustomAttribute(cab);

/*
            cab = DynamicAssemblyUtility.EmitAttribute(typeof(System.Runtime.CompilerServices.RuntimeCompatibilityAttribute), new string[] { "WrapNonExceptionThrows" }, new object[] { true });
            ab.SetCustomAttribute(cab);

            cab = DynamicAssemblyUtility.EmitAttribute(typeof(System.Diagnostics.DebuggableAttribute), new Type[] { typeof(System.Diagnostics.DebuggableAttribute.DebuggingModes) }, 
                                                       new object[] { System.Diagnostics.DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints });
            ab.SetCustomAttribute(cab);

            cab = DynamicAssemblyUtility.EmitAttribute(typeof(System.Runtime.CompilerServices.CompilationRelaxationsAttribute), new Type[] { typeof(int) },
                                                       new object[] { 8 });
            ab.SetCustomAttribute(cab);            
 */ 
        }

        private void EmitGetTableProperties(TypeBuilder tbDataContext, IList<TableClassInfo> tablesToEmit)
        {
            foreach (TableClassInfo tci in tablesToEmit)
                EmitGetTableProperty(tbDataContext, tci.TypeBuilder);
        }

        private void EmitProceduresAndFunctions(ModuleBuilder mb, TypeBuilder tbDataContext, IList<ProcedureInfo> procInfoList, string ns, IDictionary<string, TypeBuilder> typeBuilders)
        {
            foreach (ProcedureInfo pi in procInfoList)            
            {
                IDictionary<TableInfo, TypeBuilder> resultTablesTypeBuilders = new Dictionary<TableInfo, TypeBuilder>();

                int i = 1;
                foreach (TableInfo ti in pi.ReturnTables)
                {
                    string relClassName = DBObjectNameToClassName(pi) + "Result";
                    if (pi.ReturnTables.Count > 1)
                        relClassName += (i++).ToString();

                    string classFullName = GetUniqueName(GetTypeFullName(ns, relClassName), typeBuilders.Keys);
                    TableClassInfo tci = EmitTableClass(mb, ti, classFullName);
                    tci.TypeBuilder.CreateType();
                    typeBuilders.Add(tci.TypeBuilder.FullName, tci.TypeBuilder);
                    resultTablesTypeBuilders.Add(ti, tci.TypeBuilder);
                }

                EmitProcedureCallMethod(tbDataContext, pi, resultTablesTypeBuilders);
            }
        }

        private void EmitProcedureCallMethod(TypeBuilder tbDataContext, ProcedureInfo pi, IDictionary<TableInfo, TypeBuilder> resultTablesTypeBuilders)
        {
            string methodName = DynamicAssemblyUtility.GetValidIdentifier(pi.Name);

            Type retType = null;
            IList<Type> paramTypes = new List<Type>();
            foreach(SchemaInfo.ParameterInfo paramInfo in pi.Parameters)
                if (paramInfo.ParameterDir != ParameterDir.Return)
                {
                    // ValueTypes must be wrapped by Nullable<>
                    Type t;
                    if (paramInfo.DataType.IsValueType)
                        t = typeof(Nullable<>).MakeGenericType(new Type[] { paramInfo.DataType });
                    else
                        t = paramInfo.DataType;

                    // Handle output parameters with ByRef 
                    if ((paramInfo.ParameterDir & ParameterDir.Output) == ParameterDir.Output)
                        t = t.MakeByRefType();

                    paramTypes.Add(t);
                }
                else
                    retType = paramInfo.DataType;
            
            switch(pi.ReturnTables.Count)
            {
                case 0:
                    if(retType == null)
                        retType = typeof(int);
                    break;
                case 1:

                    switch(pi.Type)
                    {
                        case ProcedureType.CLRTableFunction:
                        case ProcedureType.InlineTableFunction:
                        case ProcedureType.TableFunction:
                            retType = typeof(IQueryable<>).MakeGenericType(new System.Type[] { resultTablesTypeBuilders[pi.ReturnTables[0]] });
                            break;
                        default:
                            retType = typeof(ISingleResult<>).MakeGenericType(new System.Type[] { resultTablesTypeBuilders[pi.ReturnTables[0]] });
                            break;
                    }                   
                    
                    break;
                default:
                    retType = typeof(IMultipleResults);
                    break;
            }

            Type [] paramTypesAr = new Type[paramTypes.Count]; 
            paramTypes.CopyTo(paramTypesAr, 0);

            MethodBuilder methb = tbDataContext.DefineMethod(methodName, MethodAttributes.Public, retType, paramTypesAr);

            int i = 1;
            foreach (SchemaInfo.ParameterInfo paramInfo in pi.Parameters)
                if (paramInfo.ParameterDir != ParameterDir.Return)
                {
                    // Remove the leading @
                    string paramNameTmp = paramInfo.Name.StartsWith("@") ? paramInfo.Name.Substring(1) : paramInfo.Name;
                    string paramName = DynamicAssemblyUtility.GetValidIdentifier(paramNameTmp);
                    ParameterBuilder pb = methb.DefineParameter(i, ParameterAttributes.In, paramName);

                    CustomAttributeBuilder cabp = DynamicAssemblyUtility.EmitAttribute(typeof(ParameterAttribute),
                                                                                      new string[] { "DbType" },
                                                                                      new object[] { paramInfo.DBTypeComplete });
                    pb.SetCustomAttribute(cabp);
                    i++;
                }

            bool isComposable = (pi.Type != ProcedureType.Procedure && pi.Type != ProcedureType.CLRProcedure);
            CustomAttributeBuilder cab = DynamicAssemblyUtility.EmitAttribute(typeof(FunctionAttribute),
                                                                              new string[] { "Name", 
                                                                                             "IsComposable" },
                                                                              new object[] { DynamicAssemblyUtility.CombineEscapedTSQLIdentifier(pi.SchemaName, pi.Name), 
                                                                                             isComposable });

            methb.SetCustomAttribute(cab);

            // IMultipleResults requires one ResultTypeAttribute for each table
            if (retType.Equals(typeof(IMultipleResults)))
                foreach (TableInfo ti in pi.ReturnTables)
                {
                    ConstructorInfo constructor = typeof(ResultTypeAttribute).GetConstructor(new Type[] { typeof(Type) });
                    cab = new CustomAttributeBuilder(constructor, new object[] { resultTablesTypeBuilders[ti] });
                    methb.SetCustomAttribute(cab);
                }

            ILGenerator methodIL = methb.GetILGenerator();

            if (retType.IsGenericType && retType.GetGenericTypeDefinition().Equals(typeof(IQueryable<>)))
                EmitTableFunctionMethodBody(methodIL, pi, resultTablesTypeBuilders, paramTypes);
            else
                EmitProcedureOrScalarFunctionMethodBody(methodIL, pi, resultTablesTypeBuilders, paramTypes, retType);
        }

        private static void EmitProcedureOrScalarFunctionMethodBody(ILGenerator ilg, ProcedureInfo pi, IDictionary<TableInfo, TypeBuilder> resultTablesTypeBuilders, IList<Type> paramTypes, Type retType)
        {
            //ilg.DeclareLocal(retType);
            ilg.DeclareLocal(typeof(IExecuteResult));
            ilg.DeclareLocal(typeof(object[]));

            ilg.Emit(OpCodes.Ldarg_0);
            ilg.Emit(OpCodes.Ldarg_0);

            MethodInfo getCurrentMethodMethodInfo = typeof(MethodBase).GetMethod("GetCurrentMethod", Type.EmptyTypes);
            ilg.Emit(OpCodes.Call, getCurrentMethodMethodInfo);
            ilg.Emit(OpCodes.Castclass, typeof(MethodInfo));
            ilg.Emit(OpCodes.Ldc_I4_S, paramTypes.Count);
            ilg.Emit(OpCodes.Newarr, typeof(object));
            ilg.Emit(OpCodes.Stloc_1);
            ilg.Emit(OpCodes.Ldloc_1);

            for (byte j = 0; j < paramTypes.Count; j++)
            {
                ilg.Emit(OpCodes.Ldc_I4_S, j);
                ilg.Emit(OpCodes.Ldarg_S, j + 1);

                Type t;
                if (paramTypes[j].IsByRef)
                {
                    t = paramTypes[j].GetElementType();

                    if (t.IsGenericType && t.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
                        ilg.Emit(OpCodes.Ldobj, t);
                    else
                        ilg.Emit(OpCodes.Ldind_Ref);
                }
                else
                    t = paramTypes[j];

                if ((t.IsGenericType && t.GetGenericTypeDefinition().Equals(typeof(Nullable<>))) || t.IsValueType)
                    ilg.Emit(OpCodes.Box, t);

                ilg.Emit(OpCodes.Stelem_Ref);
                ilg.Emit(OpCodes.Ldloc_1);
            }

            MethodInfo executeMethodCallMethodInfo = typeof(DataContext).GetMethod("ExecuteMethodCall", BindingFlags.Instance | BindingFlags.NonPublic, null,
                                                                                    new Type[] { typeof(object), typeof(MethodInfo), typeof(object[]) }, null);
            ilg.Emit(OpCodes.Call, executeMethodCallMethodInfo);
            ilg.Emit(OpCodes.Stloc_0);

            for (byte j = 0; j < paramTypes.Count; j++)
                if ((pi.Parameters[j].ParameterDir & ParameterDir.Output) == ParameterDir.Output)
                {
                    ilg.Emit(OpCodes.Ldarg_S, j + 1);
                    ilg.Emit(OpCodes.Ldloc_0);
                    ilg.Emit(OpCodes.Ldc_I4_S, j);
                    MethodInfo getParameterValueMethodInfo = typeof(IExecuteResult).GetMethod("GetParameterValue", new Type[] { typeof(int) });
                    ilg.Emit(OpCodes.Callvirt, getParameterValueMethodInfo);

                    Type t = paramTypes[j].GetElementType();
                    if (t.IsGenericType && t.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
                    {                        
                        ilg.Emit(OpCodes.Unbox_Any, paramTypes[j].GetElementType());
                        ilg.Emit(OpCodes.Stobj, paramTypes[j].GetElementType());
                    }
                    else
                    {
                        ilg.Emit(OpCodes.Castclass, t);
                        ilg.Emit(OpCodes.Stind_Ref);

                    }
                }            

            ilg.Emit(OpCodes.Ldloc_0);
            MethodInfo getReturnValueMethodInfo = typeof(IExecuteResult).GetProperty("ReturnValue").GetGetMethod();
            ilg.Emit(OpCodes.Callvirt, getReturnValueMethodInfo);

            if(retType.IsValueType)
                ilg.Emit(OpCodes.Unbox_Any, retType);
            else
                ilg.Emit(OpCodes.Castclass, retType);

            ilg.Emit(OpCodes.Ret);
        }

        private static void EmitTableFunctionMethodBody(ILGenerator ilg, ProcedureInfo pi, IDictionary<TableInfo, TypeBuilder> resultTablesTypeBuilders, IList<Type> paramTypes)
        {
            ilg.DeclareLocal(typeof(object[]));

            ilg.Emit(OpCodes.Ldarg_0);
            ilg.Emit(OpCodes.Ldarg_0);

            MethodInfo getCurrentMethodMethodInfo = typeof(MethodBase).GetMethod("GetCurrentMethod", Type.EmptyTypes);
            ilg.Emit(OpCodes.Call, getCurrentMethodMethodInfo);
            ilg.Emit(OpCodes.Castclass, typeof(MethodInfo));
            ilg.Emit(OpCodes.Ldc_I4_S, paramTypes.Count);
            ilg.Emit(OpCodes.Newarr, typeof(object));
            ilg.Emit(OpCodes.Stloc_0);
            ilg.Emit(OpCodes.Ldloc_0);

            for (byte j = 0; j < paramTypes.Count; j++)
            {
                ilg.Emit(OpCodes.Ldc_I4_S, j);
                ilg.Emit(OpCodes.Ldarg_S, j + 1);
                ilg.Emit(OpCodes.Box, paramTypes[j]);
                ilg.Emit(OpCodes.Stelem_Ref);
                ilg.Emit(OpCodes.Ldloc_0);
            }

            MethodInfo createMethodCallQueryMethodInfo = typeof(DataContext).GetMethod("CreateMethodCallQuery", BindingFlags.Instance | BindingFlags.NonPublic, null,
                                                                                    new Type[] { typeof(object), typeof(MethodInfo), typeof(object[]) }, null).
                                                                                    MakeGenericMethod(resultTablesTypeBuilders[pi.ReturnTables[0]]);
            ilg.Emit(OpCodes.Call, createMethodCallQueryMethodInfo);
            ilg.Emit(OpCodes.Ret);
        }

        /// <summary>
        /// 
        /// Generates properties like:
        /// 
        /// <code>
        /// public System.Data.Linq.Table&ltEmployee&gt Employees
		/// {
		/// 	get
		/// 	{
		/// 		return this.GetTable&lt;Employee&gt;();
		/// 	}
        /// }
        /// </code>
        /// </summary>
        /// <param name="mb"></param>
        /// <param name="name"></param>
        /// <param name="generatedTypes"></param>
        private TypeBuilder EmitDataContext(ModuleBuilder mb, string name)
        {
            TypeBuilder tbDataContext = mb.DefineType(name, TypeAttributes.Public | TypeAttributes.BeforeFieldInit, typeof(DataContext));

            CustomAttributeBuilder cab = DynamicAssemblyUtility.EmitAttribute(typeof(DatabaseAttribute), 
                                                                              new string[] { "Name" }, 
                                                                              new object [] { mb.Assembly.GetName().Name } );
            tbDataContext.SetCustomAttribute(cab);

            EmitDataContextConstructors(tbDataContext);

            return tbDataContext;
        }

        private static void EmitDataContextConstructors(TypeBuilder tbDataContext)
        {
            // Generate a constructor to call the base class
            Type[] parameterTypes = { typeof(string) };
            ConstructorBuilder ctor0 = tbDataContext.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, parameterTypes);
            ILGenerator ctor0IL = ctor0.GetILGenerator();
            ctor0IL.Emit(OpCodes.Ldarg_0);
            ctor0IL.Emit(OpCodes.Ldarg_1);
            ctor0IL.Emit(OpCodes.Call, typeof(DataContext).GetConstructor(parameterTypes));
            ctor0IL.Emit(OpCodes.Ret);
        }

        private void EmitGetTableProperty(TypeBuilder tbDataContext, TypeBuilder tb)
        {
            string propertyName = DataContextPropertyNameFromType(tb);

            Type retType = typeof(Table<>).MakeGenericType(new System.Type[] { tb });

            PropertyBuilder pb = tbDataContext.DefineProperty(propertyName, PropertyAttributes.HasDefault, retType, null);

            // The property "set" and property "get" methods require a special set of attributes.
            MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig;

            // Define the "get" accessor method                
            MethodBuilder getAccessor = tbDataContext.DefineMethod("get_" + propertyName, getSetAttr, retType, Type.EmptyTypes);

            ILGenerator getIL = getAccessor.GetILGenerator();

            //return base.GetTable<xxx>();
            MethodInfo getTableMethodInfo = typeof(DataContext).GetMethod("GetTable", Type.EmptyTypes).MakeGenericMethod(new System.Type[] { tb });

            getIL.Emit(OpCodes.Ldarg_0);
            getIL.Emit(OpCodes.Call, getTableMethodInfo);
            getIL.Emit(OpCodes.Ret);

            pb.SetGetMethod(getAccessor);
        }

        private string DataContextPropertyNameFromType(Type type)
        {
            return type.Name;
        }

/*
        private string GetRelativeTypeName(Type t, string ns)
        {
            return t.FullName.Substring(ns != null && ns.Length > 0 && t.FullName.StartsWith(ns) ? ns.Length + 1 : 0);
        }
 */

        private IList<TableClassInfo> EmitTables(ModuleBuilder mb, IList<TableInfo> tableInfoList, string ns, IDictionary<string, TypeBuilder> typeBuilders)
        {
            // The key is the class full name
            IList<TableClassInfo> tablesToEmit = new List<TableClassInfo>();

            foreach (TableInfo ti in tableInfoList)
            {
                string classFullName = GetUniqueName(GetTypeFullName(ns, DBObjectNameToClassName(ti)), typeBuilders.Keys);
                TableClassInfo tci = EmitTableClass(mb, ti, classFullName);
                tablesToEmit.Add(tci);
                typeBuilders.Add(tci.TypeBuilder.FullName, tci.TypeBuilder);
            }

            IList<RelationInfo> relations = new List<RelationInfo>();  

            foreach (TableClassInfo tci in tablesToEmit)
                AddTableRelations(tablesToEmit, tci, relations);

            foreach (TableClassInfo tci in tablesToEmit)
                AddTableDefaultConstructor(tci, relations);                
            
            foreach (TableClassInfo tci in tablesToEmit)
            {
                try
                {
                    // Done with this table
                    tci.TypeBuilder.CreateType();
                }
                catch (TypeLoadException)
                {
                    // Can be safely ignored?
                }                
            }

            return tablesToEmit;
        }

        private void AddTableDefaultConstructor(TableClassInfo tci, IList<RelationInfo> relations)
        {
            ConstructorBuilder ctor0 = tci.TypeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, Type.EmptyTypes);
            ILGenerator ctor0IL = ctor0.GetILGenerator();
            ctor0IL.Emit(OpCodes.Ldarg_0);
            ctor0IL.Emit(OpCodes.Call, typeof(Object).GetConstructor(Type.EmptyTypes));

            foreach (RelationInfo relInfo in relations)
            {
                // Add EntititySet<> initialization
                if (relInfo.PKClassInfo == tci)
                {
                    ctor0IL.Emit(OpCodes.Ldarg_0);
                    ctor0IL.Emit(OpCodes.Ldarg_0);
                    ctor0IL.Emit(OpCodes.Ldftn, relInfo.AttachMethod);

                    Type actionType = typeof(Action<>).MakeGenericType(new Type[] { relInfo.FKClassInfo.TypeBuilder });
                    ConstructorInfo actionCtorInfo = TypeBuilder.GetConstructor(actionType, typeof(Action<>).GetConstructor(new Type[] { typeof(object), typeof(IntPtr) }));
                    ctor0IL.Emit(OpCodes.Newobj, actionCtorInfo);

                    ctor0IL.Emit(OpCodes.Ldarg_0);
                    ctor0IL.Emit(OpCodes.Ldftn, relInfo.DetachMethod);
                    ctor0IL.Emit(OpCodes.Newobj, actionCtorInfo);

                    ConstructorInfo entitySetCtorInfo = TypeBuilder.GetConstructor(relInfo.OneToManyPropertyBuilder.PropertyType, entitySetActionTCtorInfo);
                    ctor0IL.Emit(OpCodes.Newobj, entitySetCtorInfo);

                    FieldInfo fieldInfo = tci.Fields[fieldPrefix + relInfo.OneToManyPropertyBuilder.Name];
                    ctor0IL.Emit(OpCodes.Stfld, fieldInfo);
                }

                // Add Entitityref<> initialization
                if (relInfo.FKClassInfo == tci)
                {
                    ctor0IL.Emit(OpCodes.Ldarg_0);
                    FieldInfo fieldInfo = tci.Fields[fieldPrefix + relInfo.ManyToOnePropertyBuilder.Name];
                    ctor0IL.Emit(OpCodes.Ldflda, fieldInfo);

                    Type manyToOneRelTypeObj = typeof(EntityRef<>).MakeGenericType(new System.Type[] { relInfo.PKClassInfo.TypeBuilder });
                    //Type manyToOneRelTypeObj = typeof(EntityRef<>).MakeGenericType(new System.Type[] { typeof(object) });
                    ctor0IL.Emit(OpCodes.Initobj, manyToOneRelTypeObj);
                }
            }   

            ctor0IL.Emit(OpCodes.Ret);
        }

        private void AddTableRelations(IList<TableClassInfo> tablesToEmit, TableClassInfo fkTci, IList<RelationInfo> relations)
        {
            foreach (ConstraintInfo constInfo in fkTci.TableInfo.Constraints)
                if (constInfo is ForeignKeyConstraintInfo)
                {
                    ForeignKeyConstraintInfo fkConstInfo = constInfo as ForeignKeyConstraintInfo;

                    TableClassInfo pkTci = null;
                    foreach(TableClassInfo tci in tablesToEmit)
                        if (tci.TableInfo == fkConstInfo.TargetTableInfo)
                        //if (tci.TableInfo.SchemaName == fkConstInfo.TargetTable.SchemaName && tci.TableInfo.Name == fkConstInfo.TargetTable.Name)
                        {
                            pkTci = tci;
                            break;
                        }

                    // Skip this relationship if the pk table is not included
                    if (pkTci != null)
                    {
                        RelationInfo relInfo = new RelationInfo() { PKClassInfo = pkTci, FKClassInfo = fkTci };

                        if (CheckForeignKey(fkTci, pkTci, fkConstInfo))
                        {
                            AddRelationToPKClass(pkTci, fkTci, fkConstInfo, relInfo);
                            AddRelationToFKClass(pkTci, fkTci, fkConstInfo, relInfo);
                            AddAttachDetachMethodsToPKClass(pkTci, fkTci, fkConstInfo, relInfo);

                            relations.Add(relInfo);
                        }
                    }
                }            
        }

        private bool CheckForeignKey(TableClassInfo fkTci, TableClassInfo pkTci, ForeignKeyConstraintInfo fkConstInfo)
        {
            bool fkOk = false;

            try
            {
                // Skip all constraints where the PK of the FK table has missing columns
                CheckPKColumns(fkTci, fkConstInfo.Name);

                // Check for all FK columns
                foreach (string fkColumnName in fkConstInfo.ColumnNames)
                    if (!fkTci.ColumnNameProperty.ContainsKey(fkColumnName))
                        throw new L2SQLDataModelException(string.Format("Foreign key relationship \"{0}\" skipped because of missing column \"{1}\" in table \"{2}\", please check previous errors",
                                                                        fkConstInfo.Name, fkColumnName, 
                                                                        DynamicAssemblyUtility.CombineEscapedTSQLIdentifier(fkTci.TableInfo.SchemaName, fkTci.TableInfo.Name)));

                // Check for all target table constraint columns
                foreach (string pkColumnName in fkConstInfo.TargetConstraint.ColumnNames)
                    if (!pkTci.ColumnNameProperty.ContainsKey(pkColumnName))
                        throw new L2SQLDataModelException(string.Format("Foreign key relationship \"{0}\" in table \"{1}\" skipped because of missing column \"{2}\" in the target key of table \"{3}\", please check previous errors",
                                                                        fkConstInfo.Name,
                                                                        DynamicAssemblyUtility.CombineEscapedTSQLIdentifier(fkTci.TableInfo.SchemaName, fkTci.TableInfo.Name),
                                                                        pkColumnName,
                                                                        DynamicAssemblyUtility.CombineEscapedTSQLIdentifier(pkTci.TableInfo.SchemaName, pkTci.TableInfo.Name)));

                // Skip also all constraints where the PK of the target table has missing columns
                if (!fkConstInfo.TargetConstraint.IsPrimaryKey)
                    CheckPKColumns(pkTci, fkConstInfo.Name);

                fkOk = true;
            }
            catch (Exception ex)
            {
                HandleWarningException(ex);
            }

            return fkOk;
        }

        private static void CheckPKColumns(TableClassInfo tci, string fkConstraintName)
        {
            foreach (ConstraintInfo ci in tci.TableInfo.Constraints)
                if (ci is UniqueConstraintInfo && (ci as UniqueConstraintInfo).IsPrimaryKey)
                    foreach (string pkColumnName in (ci as UniqueConstraintInfo).ColumnNames)
                        if (!tci.ColumnNameProperty.ContainsKey(pkColumnName))
                            throw new L2SQLDataModelException(string.Format("Foreign key relationship \"{0}\" skipped because of missing primary key column \"{1}\" in table \"{2}\", please check previous errors",
                                                                            fkConstraintName, pkColumnName,
                                                                            DynamicAssemblyUtility.CombineEscapedTSQLIdentifier(tci.TableInfo.SchemaName, tci.TableInfo.Name)));
        }


        private void AddAttachDetachMethodsToPKClass(TableClassInfo pkTci, TableClassInfo fkTci, ForeignKeyConstraintInfo fkConstInfo, RelationInfo relInfo)
        {
            MethodBuilder attachMethod = pkTci.TypeBuilder.DefineMethod(attachPrefix + relInfo.OneToManyPropertyBuilder.Name,
                                                                       MethodAttributes.Private, typeof(void), new Type [] { fkTci.TypeBuilder });

            ILGenerator attachIL = attachMethod.GetILGenerator();

            attachIL.Emit(OpCodes.Ldarg_1);
            attachIL.Emit(OpCodes.Ldarg_0);

            MethodInfo manyToOneRelSetMethodInfo = relInfo.ManyToOnePropertyBuilder.GetSetMethod();
            attachIL.Emit(OpCodes.Callvirt, manyToOneRelSetMethodInfo);
            attachIL.Emit(OpCodes.Ret);


            MethodBuilder detachMethod = pkTci.TypeBuilder.DefineMethod(detachPrefix + relInfo.OneToManyPropertyBuilder.Name,
                                                                       MethodAttributes.Private, typeof(void), new Type[] { fkTci.TypeBuilder });

            ILGenerator detachIL = detachMethod.GetILGenerator();

            detachIL.Emit(OpCodes.Ldarg_1);
            detachIL.Emit(OpCodes.Ldnull);  
            detachIL.Emit(OpCodes.Callvirt, manyToOneRelSetMethodInfo);
            detachIL.Emit(OpCodes.Ret);

            relInfo.AttachMethod = attachMethod;
            relInfo.DetachMethod = detachMethod;

        }

        private void AddRelationToFKClass(TableClassInfo pkTci, TableClassInfo fkTci, ForeignKeyConstraintInfo fkConstInfo, RelationInfo relInfo)
        {
            // The navigation property name is base on the name of the class
            string propertyName = GetUniqueName(pkTci.TypeBuilder.Name, fkTci.Properties.Keys);

            Type fieldType = typeof(EntityRef<>).MakeGenericType(new System.Type[] { pkTci.TypeBuilder });

            // Create a private field accessed by the property
            FieldBuilder fb = fkTci.TypeBuilder.DefineField(fieldPrefix + propertyName, fieldType, FieldAttributes.Private);
            PropertyBuilder pb = fkTci.TypeBuilder.DefineProperty(propertyName, PropertyAttributes.HasDefault, pkTci.TypeBuilder, null);

            fkTci.Fields.Add(fb.Name, fb);
            fkTci.Properties.Add(pb.Name, pb);
            relInfo.ManyToOnePropertyBuilder = pb;

            // The property "set" and property "get" methods require a special set of attributes.
            MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig;

            // Define the "get" accessor method
            MethodBuilder getAccessor = fkTci.TypeBuilder.DefineMethod("get_" + propertyName, getSetAttr, pkTci.TypeBuilder, Type.EmptyTypes);

            ILGenerator getIL = getAccessor.GetILGenerator();
            // For an instance property, argument zero is the instance
            getIL.Emit(OpCodes.Ldarg_0);
            getIL.Emit(OpCodes.Ldflda, fb);

            MethodInfo getEntityMethodInfo = TypeBuilder.GetMethod(fieldType, typeof(EntityRef<>).GetProperty("Entity").GetGetMethod());
            getIL.Emit(OpCodes.Call, getEntityMethodInfo);
            getIL.Emit(OpCodes.Ret);

            // Define the "set" accessor method for Number, which has no return
            // type and takes one argument of type int (Int32).
            MethodBuilder setAccessor = fkTci.TypeBuilder.DefineMethod("set_" + propertyName, getSetAttr, null, new Type[] { pkTci.TypeBuilder });

            ILGenerator setIL = setAccessor.GetILGenerator();

            setIL.DeclareLocal(pkTci.TypeBuilder);

            Label label0 = setIL.DefineLabel();
            Label label1 = setIL.DefineLabel();
            Label label2 = setIL.DefineLabel();
            Label label3 = setIL.DefineLabel();

            // Load the instance and then the numeric argument, then store the
            // argument in the field.
            setIL.Emit(OpCodes.Ldarg_0);
            setIL.Emit(OpCodes.Ldflda, fb);
            setIL.Emit(OpCodes.Call, getEntityMethodInfo);
            setIL.Emit(OpCodes.Stloc_0);
            setIL.Emit(OpCodes.Ldloc_0); 
            setIL.Emit(OpCodes.Ldarg_1);
            setIL.Emit(OpCodes.Bne_Un_S, label0);
            setIL.Emit(OpCodes.Ldarg_0);
            setIL.Emit(OpCodes.Ldflda, fb);

            MethodInfo getHasLoadedOrAssignedValueMethodInfo = TypeBuilder.GetMethod(fieldType, typeof(EntityRef<>).GetProperty("HasLoadedOrAssignedValue").GetGetMethod());
            setIL.Emit(OpCodes.Call, getHasLoadedOrAssignedValueMethodInfo);
            setIL.Emit(OpCodes.Brtrue_S, label1);
            
            setIL.MarkLabel(label0);
                       
            setIL.Emit(OpCodes.Ldloc_0);
            setIL.Emit(OpCodes.Brfalse_S, label2);

            setIL.Emit(OpCodes.Ldarg_0);
            setIL.Emit(OpCodes.Ldflda, fb);
            setIL.Emit(OpCodes.Ldnull);

            MethodInfo setEntityMethodInfo = TypeBuilder.GetMethod(fieldType, typeof(EntityRef<>).GetProperty("Entity").GetSetMethod());
            setIL.Emit(OpCodes.Call, setEntityMethodInfo);
            setIL.Emit(OpCodes.Ldloc_0);

            PropertyBuilder pkPropertyBuilder = pkTci.ColumnNameProperty[fkConstInfo.TargetConstraint.ColumnNames[0]];
            
            string fkFieldName = fieldPrefix + fkTci.ColumnNameProperty[fkConstInfo.ColumnNames[0]].Name;
            FieldBuilder  fkFieldBuilder = fkTci.Fields[fkFieldName];

            MethodInfo getEmployeesMethodInfo = relInfo.OneToManyPropertyBuilder.GetGetMethod();
            setIL.Emit(OpCodes.Callvirt, getEmployeesMethodInfo);
            setIL.Emit(OpCodes.Ldarg_0);

            MethodInfo removeMethodInfo = TypeBuilder.GetMethod(relInfo.OneToManyPropertyBuilder.PropertyType, typeof(EntitySet<>).GetMethod("Remove"));
            setIL.Emit(OpCodes.Callvirt, removeMethodInfo);
            setIL.Emit(OpCodes.Pop);

            setIL.MarkLabel(label2);
            setIL.Emit(OpCodes.Ldarg_0);
            setIL.Emit(OpCodes.Ldflda, fb);
            setIL.Emit(OpCodes.Ldarg_1);
            setIL.Emit(OpCodes.Call, setEntityMethodInfo);
            setIL.Emit(OpCodes.Ldarg_1);
            setIL.Emit(OpCodes.Brfalse_S, label3);

            setIL.Emit(OpCodes.Ldarg_1);
            setIL.Emit(OpCodes.Callvirt, getEmployeesMethodInfo);
            setIL.Emit(OpCodes.Ldarg_0);
            
            MethodInfo addMethodInfo = TypeBuilder.GetMethod(relInfo.OneToManyPropertyBuilder.PropertyType, typeof(EntitySet<>).GetMethod("Add"));
            setIL.Emit(OpCodes.Callvirt, addMethodInfo);
            setIL.Emit(OpCodes.Ldarg_0);
            setIL.Emit(OpCodes.Ldarg_1);

            MethodInfo getEmployeeIDMethodInfo = pkPropertyBuilder.GetGetMethod();
            setIL.Emit(OpCodes.Callvirt, getEmployeeIDMethodInfo);

            if (fkFieldBuilder.FieldType.IsGenericType && fkFieldBuilder.FieldType.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
            {
                ConstructorInfo fkConstructorInfo = fkFieldBuilder.FieldType.GetConstructors()[0];
                setIL.Emit(OpCodes.Newobj, fkConstructorInfo);
            }
                        
            setIL.Emit(OpCodes.Stfld, fkFieldBuilder);
            setIL.Emit(OpCodes.Ret);

            setIL.MarkLabel(label3);
            setIL.Emit(OpCodes.Ldarg_0);

            setIL.Emit(OpCodes.Ldflda, fkFieldBuilder);
            setIL.Emit(OpCodes.Initobj, fkFieldBuilder.FieldType);

            setIL.MarkLabel(label1);
            setIL.Emit(OpCodes.Ret);

            // Last, map the "get" and "set" accessor methods to the 
            // PropertyBuilder. The property is now complete. 
            pb.SetGetMethod(getAccessor);
            pb.SetSetMethod(setAccessor);

            string fkKey;
            string pkKey;
            GetCommaSeparatedKeys(fkConstInfo, pkTci, fkTci, out fkKey, out pkKey);

            CustomAttributeBuilder cab = DynamicAssemblyUtility.EmitAttribute(typeof(AssociationAttribute),
                                                                              new string[] { "Name", "Storage", 
                                                                                             "ThisKey", "OtherKey", "IsForeignKey" },
                                                                              new object[] { fkConstInfo.Name, fieldPrefix + propertyName, 
                                                                                             fkKey, pkKey, true });
            pb.SetCustomAttribute(cab);
        }


        private static string GetUniqueName(string name, IEnumerable<string> existingNames)
        {
            int i = 1;
            string baseName = name;
            while (existingNames.Contains(name))
                name = baseName + i++.ToString();
            return name;
        }

        private void AddRelationToPKClass(TableClassInfo pkTci, TableClassInfo fkTci, ForeignKeyConstraintInfo fkConstInfo, RelationInfo relInfo)
        {
            string propertyName = GetUniqueName(fkTci.TypeBuilder.Name, pkTci.Properties.Keys);

            PropertyBuilder pb;
            FieldBuilder fb;
            AddEntitySetProperty(pkTci.TypeBuilder, fkTci.TypeBuilder, propertyName, out pb, out fb);
            pkTci.Properties.Add(pb.Name, pb);
            pkTci.Fields.Add(fb.Name, fb);

            relInfo.OneToManyPropertyBuilder = pb;

            string fkKey;
            string pkKey;
            GetCommaSeparatedKeys(fkConstInfo, pkTci, fkTci, out fkKey, out pkKey);

            CustomAttributeBuilder cab = DynamicAssemblyUtility.EmitAttribute(typeof(AssociationAttribute),
                                                                              new string[] { "Name", "Storage", 
                                                                                             "ThisKey", "OtherKey" },
                                                                              new object[] { fkConstInfo.Name, fieldPrefix + propertyName, 
                                                                                             pkKey, fkKey });
            pb.SetCustomAttribute(cab);            
        }

        private void AddEntitySetProperty(TypeBuilder tb, Type entityType, string name, out PropertyBuilder pb, out FieldBuilder fb)
        {
            Type fieldType = typeof(EntitySet<>).MakeGenericType(new System.Type[] { entityType });

            // Create a private field accessed by the property
            fb = tb.DefineField(fieldPrefix + name, fieldType, FieldAttributes.Private);
            //fb = tb.DefineField(fieldPrefix + name, fieldType, FieldAttributes.Public);

            pb = tb.DefineProperty(name, PropertyAttributes.HasDefault, fieldType, null);

            // The property "set" and property "get" methods require a special set of attributes.
            MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig;

            // Define the "get" accessor method
            MethodBuilder getAccessor = tb.DefineMethod("get_" + name, getSetAttr, fieldType, Type.EmptyTypes);

            ILGenerator getIL = getAccessor.GetILGenerator();
            // For an instance property, argument zero is the instance. Load the 
            // instance, then load the private field and return, leaving the
            // field value on the stack.
            getIL.Emit(OpCodes.Ldarg_0);
            getIL.Emit(OpCodes.Ldfld, fb);
            getIL.Emit(OpCodes.Ret);

            // Define the "set" accessor method for Number, which has no return
            // type and takes one argument of type int (Int32).
            MethodBuilder setAccessor = tb.DefineMethod("set_" + name, getSetAttr, null, new Type[] { fieldType });

            ILGenerator setIL = setAccessor.GetILGenerator();
            // Load the instance and then the numeric argument, then store the
            // argument in the field.
            setIL.Emit(OpCodes.Ldarg_0);
            setIL.Emit(OpCodes.Ldfld, fb);
            setIL.Emit(OpCodes.Ldarg_1);

            MethodInfo assignMethodInfo = TypeBuilder.GetMethod(fieldType, typeof(EntitySet<>).GetMethod("Assign"));

            setIL.Emit(OpCodes.Callvirt, assignMethodInfo);
            setIL.Emit(OpCodes.Ret);

            // Last, map the "get" and "set" accessor methods to the 
            // PropertyBuilder. The property is now complete. 
            pb.SetGetMethod(getAccessor);
            pb.SetSetMethod(setAccessor);
        }

        private static void GetCommaSeparatedKeys(ForeignKeyConstraintInfo fkConstInfo, TableClassInfo pkTci, TableClassInfo fkTci, out string fkKey, out string pkKey)
        {            
            fkKey = "";
            foreach (string fkColumnName in fkConstInfo.ColumnNames)
            {
                if (fkKey.Length > 0)
                    fkKey += ",";
                fkKey += fkTci.ColumnNameProperty[fkColumnName].Name;
            }

            pkKey = "";
            foreach (string pkColumnName in fkConstInfo.TargetConstraint.ColumnNames)
            { 
                if (pkKey.Length > 0)
                    pkKey += ",";
                pkKey += pkTci.ColumnNameProperty[pkColumnName].Name;
            }
        }

        private string GetTypeFullName(string ns, string typeName)
        {
            return (ns != null ? ns + "." : "") + typeName;
        }

        private TableClassInfo EmitTableClass(ModuleBuilder mb, TableInfo ti, string classFullName)
        {
            TypeBuilder tb = mb.DefineType(classFullName, TypeAttributes.Public | TypeAttributes.BeforeFieldInit | TypeAttributes.AnsiClass);

            TableClassInfo tci = new TableClassInfo(tb, ti);

            if (ti.Name != null)
            {
                string tableAndSchemaName = DynamicAssemblyUtility.CombineEscapedTSQLIdentifier(ti.SchemaName, ti.Name);
                CustomAttributeBuilder cab = DynamicAssemblyUtility.EmitAttribute(typeof(TableAttribute),
                                                                                  new string[] { "Name" },
                                                                                  new object[] { tableAndSchemaName });
                tb.SetCustomAttribute(cab);
            }

            IList<string> pkColumnNames = null;
            foreach (ConstraintInfo constInfo in ti.Constraints)
                if (constInfo is UniqueConstraintInfo && (constInfo as UniqueConstraintInfo).IsPrimaryKey)
                {
                    pkColumnNames = (constInfo as UniqueConstraintInfo).ColumnNames;
                    break;
                }

            foreach (ColumnInfo colInfo in ti.Columns)
            {
                bool isPrimaryKey = (pkColumnNames != null && pkColumnNames.Contains(colInfo.Name));
                EmitColumnProperty(tci, colInfo, isPrimaryKey);
            }

            return tci;
        }

        private void EmitColumnProperty(TableClassInfo tci, ColumnInfo colInfo, bool isPrimaryKey)
        {
            IDictionary<string, object> properties = new Dictionary<string, object>();

            // Handle nullable value types
            Type type = null;
            if (colInfo.DataType.IsValueType && colInfo.AllowNull)
                type = typeof(Nullable<>).MakeGenericType(new Type[] { colInfo.DataType });
            else if (colInfo.DataType.Equals(typeof(byte[])) && colInfo.DBTypeComplete == "Image")
            {
                // Change type for Image sql data types
                type = typeof(System.Data.Linq.Binary);
            }
            else
                type = colInfo.DataType;

            string propertyName = GetUniqueName(DBColumnNameToPropertyName(colInfo.Name), tci.Properties.Keys);
            
            PropertyBuilder pb;
            FieldBuilder fb;
            DynamicAssemblyUtility.EmitSimpleProperty(tci.TypeBuilder, propertyName, type, fieldPrefix, out pb, out fb);

            properties.Add("Storage", fieldPrefix + pb.Name);

            if (colInfo.DBTypeComplete != null)
                properties.Add("DbType", colInfo.DBTypeComplete);

            switch (colInfo.DBTypeComplete)
            {
                case "Image":
                case "Text":
                case "NText":
                    properties.Add("UpdateCheck", UpdateCheck.Never);
                    break;
            }

            //if (propertyName != colInfo.Name)
            properties.Add("Name", DynamicAssemblyUtility.EscapeTSQLIdentifier(colInfo.Name));

            if (!colInfo.AllowNull)
                properties.Add("CanBeNull", false);

            if(isPrimaryKey)
                properties.Add("IsPrimaryKey", true);

            if (colInfo.IsAutoInc)
            {
                properties.Add("IsDbGenerated", true);
                properties.Add("AutoSync", AutoSync.OnInsert);
            }

            if (colInfo.IsComputed)
            {
                properties.Add("IsDbGenerated", true);
                properties.Add("AutoSync", AutoSync.Always);
            }

            CustomAttributeBuilder cabProperty = DynamicAssemblyUtility.EmitAttribute(typeof(ColumnAttribute), properties);
            pb.SetCustomAttribute(cabProperty);

            tci.Properties.Add(pb.Name, pb);
            tci.Fields.Add(fb.Name, fb);
            tci.ColumnNameProperty.Add(colInfo.Name, pb);
        }

        private string DBObjectNameToClassName(DBObjectInfo objInfo)
        {
            // if the schema is not dbo, add it to the namespace to avoid duplicates
            return (objInfo.SchemaName.ToLower() == "dbo" ? "" : DynamicAssemblyUtility.GetValidIdentifier(objInfo.SchemaName) + ".") + 
                   //DynamicAssemblyUtility.CapitalizeString(DynamicAssemblyUtility.GetValidIdentifier(ti.Name));
                   DynamicAssemblyUtility.GetValidIdentifier(objInfo.Name);
        }

        private string DBColumnNameToPropertyName(string columnName)
        {
            //return DynamicAssemblyUtility.CapitalizeString(DynamicAssemblyUtility.GetValidIdentifier(columnName));
            return DynamicAssemblyUtility.GetValidIdentifier(columnName);
        }

        /// <summary>
        /// Handle non fatal errors.
        /// </summary>
        /// <param name="ex"></param>
        private void HandleWarningException(Exception ex)
        {
            if (HandleError != null)
                HandleError(this, new DBSchemaInfoErrorEventArgs(ex));
        }
    }
}
